#include <iostream>
#include <string>
#include <stdlib.h>
#include <math.h>
#include <stdio.h>

#include "helper.hpp"
#include "interface.hpp"
#include "config_parser.hpp"

extern std::map<std::string, Base*> gids;
extern std::map<std::string, Base> vars;
extern std::map<std::string, Mode> modes;
extern std::map<std::string, Panel> panels;
extern std::map<std::string, Sensor> sensors;
extern std::map<std::string, Command> commands;
extern std::map<std::string, Device> other_devices;
extern std::map<std::string, Timer> user_timers;
extern std::map<std::string, Gsm> gsm_cmds;

/* extract g[*]:value pair, and push it into vector with pairs. */
void extract(const std::string& str, 
        std::vector< std::pair<std::string, std::string> >& vec)
{
    std::vector<std::string> tokens;
    tokenize(str, " ", tokens);
    for(size_t i = 0; i < tokens.size(); i++){
        size_t pos = tokens[i].find_first_of(":");
        if(pos == std::string::npos){
            std::cerr << "Wrong config file: " << __LINE__ << "#" << str << std::endl;
            exit(-1);
        }
        std::pair<std::string, std::string> tmp;
        tmp.first =tokens[i].substr(0, pos); 
        tmp.second =tokens[i].substr(pos + 1); 
        vec.push_back(tmp);
    } 
}

void extract_expr_true_false(std::string etf, Commands_info &cmd_inf)
{
    size_t second_sharp;
    size_t third_sharp;

    std::string expr;
    std::string if_true;
    std::string if_false;

    second_sharp = etf.find_first_of('#', 1);
    if(second_sharp == std::string::npos){
        std::cout << "Wrong config file: " << __LINE__ << std::endl;
    }

    third_sharp = etf.find_first_of('#', second_sharp + 1);
    if(third_sharp == std::string::npos){
        std::cout << "Wrong config file: " << __LINE__ << std::endl;
    }

    expr = etf.substr(1, second_sharp - 1);
    if_true = etf.substr(second_sharp + 1, third_sharp - second_sharp - 1);
    if_false = etf.substr(third_sharp + 1);

    cmd_inf.expr = expr;
    extract(if_true, cmd_inf.if_true);
    extract(if_false, cmd_inf.if_false);

    //std::cout << "EXPR:"<< expr << "\nif_true:" << if_true << "\nif_false:" << if_false << std::endl;
}

void get_var(const xmlpp::Node* node)
{
	const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node);
    Base b;

	if(nodeElement) {
		/* A mode Element node. */
		const xmlpp::Attribute* attribute;
		attribute = nodeElement->get_attribute("name");
		if(attribute) {
			b.name = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("gid");
		if(attribute) {
			b.gid = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("value");
		if(attribute) {
			b.value = attribute->get_value();
		}
    } else {
        std::cout << "Wrong config file: " << __LINE__;
    }

    vars[b.gid] = b;
    gids[b.gid] = &vars[b.gid];
}

void get_sensor(const xmlpp::Node* node, Panel &p)
{
	const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node);
    Sensor s;

	if(nodeElement) {
		/* A panel Element node. */
		const xmlpp::Attribute* attribute;
		attribute = nodeElement->get_attribute("name");
		if(attribute) {
			s.name = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("gid");
        if(attribute) {
            s.gid = attribute->get_value();
            p.s_gids.push_back(s.gid);
        }
		attribute = nodeElement->get_attribute("id");
		if(attribute) {
			s.id = atoi(std::string(attribute->get_value()).c_str());
		}
		attribute = nodeElement->get_attribute("type");
		if(attribute) {
			s.type = attribute->get_value();
		}
    } else {
        std::cout << "Wrong config file: " << __LINE__ << std::endl;
    }

    sensors[s.gid] = s;
    gids[s.gid] = &sensors[s.gid];
}

void get_gsm_cmd(const xmlpp::Node* node)
{
	const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node);
    Gsm g;

	if(nodeElement) {
		/* A Command Element node. */
		const xmlpp::Attribute* attribute;
		attribute = nodeElement->get_attribute("name");
		if(attribute) {
			g.name = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("gid");
        if(attribute) {
            g.gid = attribute->get_value();
        }
		attribute = nodeElement->get_attribute("number");
        if(attribute) {
            g.number = attribute->get_value();
        }
		attribute = nodeElement->get_attribute("text");
        if(attribute) {
            g.text = attribute->get_value();
        }
    } else {
        std::cout << "Wrong config file: " << __LINE__;
    }

    gsm_cmds[g.gid] = g;
    gids[g.gid] = &gsm_cmds[g.gid];
}

void get_panel(const xmlpp::Node* node)
{
	const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node);
    Panel p;

	if(nodeElement) {
		/* A panel Element node. */
		const xmlpp::Attribute* attribute;
		attribute = nodeElement->get_attribute("name");
		if(attribute) {
			p.name = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("gid");
		if(attribute) {
			p.gid = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("port");
		if(attribute) {
			p.port = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("init");
		if(attribute) {
			p.init_data = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("update");
		if(attribute) {
			p.update_data = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("timeout");
		if(attribute) {
			float f = atof(std::string(attribute->get_value()).c_str());
			float fractional;
			float integral;
			fractional = modff(f, &integral);
			p.timeout.it_value.tv_sec = (int)integral;
			p.timeout.it_value.tv_nsec = (long)lrintf((fractional) * 1000) * 1000000;
			p.timeout.it_interval.tv_sec = 0;
			p.timeout.it_interval.tv_nsec = 0;
		}
	}

	/* Recurse through the panel's sensors. */
	const xmlpp::TextNode* nodeText;
    xmlpp::Node::NodeList::iterator iter;
	xmlpp::Node::NodeList node_list = node->get_children();
	for(iter = node_list.begin(); iter != node_list.end(); ++iter) {
		nodeText = dynamic_cast<const xmlpp::TextNode*>(*iter);
		// Skip empty lines and indents.
		if(nodeText && nodeText->is_white_space()){
			continue;
		}
        get_sensor(*iter, p);
    }

    panels[p.gid] = p;
    gids[p.gid] = &panels[p.gid];
}

void get_timer(const xmlpp::Node* node)
{
	const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node);
    Timer t;

	if(nodeElement) {
		/* A Command Element node. */
		const xmlpp::Attribute* attribute;
		attribute = nodeElement->get_attribute("name");
		if(attribute) {
			t.name = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("gid");
        if(attribute) {
            t.gid = attribute->get_value();
        }
		attribute = nodeElement->get_attribute("state");
        if(attribute) {
            t.state = attribute->get_value();
        }
		attribute = nodeElement->get_attribute("timeout");
		if(attribute){
            float f = atof(std::string(attribute->get_value()).c_str());
			float fractional;
			float integral;
			fractional = modff(f, &integral);
			t.timeout.it_value.tv_sec = (int)integral;
			t.timeout.it_value.tv_nsec = (long)lrintf((fractional) * 1000) * 1000000;
			t.timeout.it_interval.tv_sec = 0;
			t.timeout.it_interval.tv_nsec = 0;
		}
		attribute = nodeElement->get_attribute("actions");
		if(attribute) {
            extract(attribute->get_value(), t.actions);
		}
    } else {
        std::cout << "Wrong config file: " << __LINE__;
    }

    user_timers[t.gid] = t;
    gids[t.gid] = &user_timers[t.gid];
}

void get_device(const xmlpp::Node* node)
{
	const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node);
    Device d;

	if(nodeElement) {
		/* A Command Element node. */
		const xmlpp::Attribute* attribute;
		attribute = nodeElement->get_attribute("name");
		if(attribute) {
			d.name = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("gid");
        if(attribute) {
            d.gid = attribute->get_value();
        }
		attribute = nodeElement->get_attribute("port");
		if(attribute) {
			d.port = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("data_for_set");
		if(attribute) {
			d.data_for_set = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("data_for_get");
		if(attribute) {
			d.data_for_get = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("start_byte");
		if(attribute) {
			d.start_byte = atoi(attribute->get_value().c_str()) - 1;
		}
		attribute = nodeElement->get_attribute("byte_count");
		if(attribute) {
			d.byte_count = atoi(attribute->get_value().c_str());
		}
    } else {
        std::cout << "Wrong config file: " << __LINE__;
    }

    other_devices[d.gid] = d;
    gids[d.gid] = &other_devices[d.gid];
}

void get_command(const xmlpp::Node* node)
{
	const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node);
    Command c;

	if(nodeElement) {
		/* A Command Element node. */
		const xmlpp::Attribute* attribute;
		attribute = nodeElement->get_attribute("name");
		if(attribute) {
			c.name = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("gid");
        if(attribute) {
            c.gid = attribute->get_value();
        }
		attribute = nodeElement->get_attribute("port");
		if(attribute) {
			c.port = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("data_for_set");
		if(attribute) {
			c.data_for_set = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("data_for_get");
		if(attribute) {
			c.data_for_get = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("uniq");
		if(attribute) {
			c.uniq = attribute->get_value();
		}
    } else {
        std::cout << "Wrong config file: " << __LINE__;
    }

    commands[c.gid] = c;
    gids[c.gid] = &commands[c.gid];
}

void get_condition(const xmlpp::Node* node, Mode &m)
{
	const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node);
    Condition cnd;

	if(nodeElement) {
		/* A panel Element node. */
		const xmlpp::Attribute* attribute;
		attribute = nodeElement->get_attribute("expr");
		if(attribute) {
			cnd.expr  = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("true");
        if(attribute) {
            std::string tmp = attribute->get_value();
            if(tmp[0] == '#'){
                extract_expr_true_false(tmp, cnd.true_cmds);
                cnd.true_cmds.type = CONDITIONAL;
            } else {
                cnd.true_cmds.type = SAMPLE;
                extract(tmp, cnd.true_cmds.sample);
            }
        }
		attribute = nodeElement->get_attribute("false");
        if(attribute) {
            std::string tmp = attribute->get_value();
            if(tmp[0] == '#'){
                extract_expr_true_false(tmp, cnd.false_cmds);
                cnd.false_cmds.type = CONDITIONAL;
            } else {
                cnd.false_cmds.type = SAMPLE;
                extract(tmp, cnd.false_cmds.sample);
            }
        }
		attribute = nodeElement->get_attribute("timeout");
		if(attribute){
            float f = atof(std::string(attribute->get_value()).c_str());
			float fractional;
			float integral;
			fractional = modff(f, &integral);
			cnd.timeout.it_value.tv_sec = (int)integral;
			cnd.timeout.it_value.tv_nsec = (long)lrintf((fractional) * 1000) * 1000000;
			cnd.timeout.it_interval.tv_sec = 0;
			cnd.timeout.it_interval.tv_nsec = 0;
		}
		attribute = nodeElement->get_attribute("step");
		if(attribute) {
			float f = atof(std::string(attribute->get_value()).c_str());
			float fractional;
			float integral;
			fractional = modff(f, &integral);
			cnd.step_timeout.it_value.tv_sec = (int)integral;
			cnd.step_timeout.it_value.tv_nsec = (long)lrintf((fractional) * 1000) * 1000000;
			cnd.step_timeout.it_interval.tv_sec = 0;
			cnd.step_timeout.it_interval.tv_nsec = 0;
		}
    } else {
        std::cout << "Wrong config file: " << __LINE__ << std::endl;
    }
    m.cnds.push_back(cnd);
}

void get_mode(const xmlpp::Node* node)
{
	const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node);
    Mode m;

	if(nodeElement) {
		/* A mode Element node. */
		const xmlpp::Attribute* attribute;
		attribute = nodeElement->get_attribute("name");
		if(attribute) {
			m.name = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("gid");
		if(attribute) {
			m.gid = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("state");
		if(attribute) {
			m.state = attribute->get_value();
		}
		attribute = nodeElement->get_attribute("actions");
		if(attribute) {
            extract(attribute->get_value(), m.actions);
		}
    } else {
        std::cout << "Wrong config file: " << __LINE__;
    }

	/* Recurse through the mode's conditions. */
	const xmlpp::TextNode* nodeText;
    xmlpp::Node::NodeList::iterator iter;
	xmlpp::Node::NodeList node_list = node->get_children();
	for(iter = node_list.begin(); iter != node_list.end(); ++iter) {
		nodeText = dynamic_cast<const xmlpp::TextNode*>(*iter);
		// Skip empty lines and indents.
		if(nodeText && nodeText->is_white_space()){
			continue;
		}
        get_condition(*iter, m);
    }

    modes[m.gid] = m;
    gids[m.gid] = &modes[m.gid];
}

void parse_config(std::string xml_filepath)
{
    xmlpp::DomParser xml_doc;
    const xmlpp::Node* node;
    const xmlpp::TextNode* nodeText;
    xmlpp::Node::NodeList node_list, l;
    xmlpp::Node::NodeList::iterator iter, it;
    std::string node_name;

    xml_doc.set_validate(); 
    xml_doc.parse_file(xml_filepath);
    node = xml_doc.get_document()->get_root_node();

    /* Get the "panels" and recurse through it's child. */
    node_list = node->get_children();
    for(iter = node_list.begin(); iter != node_list.end(); ++iter) {
        nodeText = dynamic_cast<const xmlpp::TextNode*>(*iter);
        // Skip empty lines and indents.
        if(nodeText && nodeText->is_white_space()){
            continue;
        }
        /* Recurse through it's child. */
        l = (*iter)->get_children();
        for(it = l.begin(); it != l.end(); ++it) {
            nodeText = dynamic_cast<const xmlpp::TextNode*>(*it);
            // Skip empty lines and indents.
            if(nodeText && nodeText->is_white_space()){
                continue;
            }
            node_name = (*it)->get_name();
            if(node_name == "panel"){
                get_panel(*it);
            } else if(node_name == "command"){
                get_command(*it);
            } else if(node_name == "mode"){
                get_mode(*it);
            } else if(node_name == "var"){
                get_var(*it);
            } else if(node_name == "device"){
                get_device(*it);
            } else if(node_name == "timer"){
                get_timer(*it);
            } else if(node_name == "gsm_cmd"){
                get_gsm_cmd(*it);
            }
        }
    }
}
